-
-
Notifications
You must be signed in to change notification settings - Fork 85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: flush only once per calls to Factory::create()
in userland
#836
base: 2.3.x
Are you sure you want to change the base?
Conversation
Factory::create()
in userland
95947f7
to
4292296
Compare
98003a3
to
b892369
Compare
5316d45
to
0ee9fb8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great!
639a303
to
88620c3
Compare
88620c3
to
a4d38a8
Compare
28ca1e3
to
5927967
Compare
5927967
to
0fbfdee
Compare
After a lot of digging, I finally got something working on my different projects, I added a lot of test cases to prevent a lot of edge cases I encountered along the way 😅 @mmarton @tandev @aegypius I'd really appreciate if you could test this branch and report any bug, before I realease this PR which changed quite a lot of things 😅 🙏 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just cleaned a little bit how we get "inversedRelationshipMetadata", hence these changes - I needed to have more information (we did only get "one to many" and "inversed one to one" but I now needed also "normal one to one"
if ($inverseRelationship instanceof OneToOneRelationship) { | ||
$this->tempAfterInstantiate[] = static function(object $newObject) use ($object, $inverseRelationship) { | ||
try { | ||
set($object, $inverseRelationship->inverseField(), $newObject); | ||
} catch (\Throwable) { | ||
} | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$address = static::addressFactory()
->afterInstantiate(
static function(Address $a): void {
static::contactFactory()->create(['address' => $a]);
}
)->create();
self::assertNotNull($address->getContact());
it was a fluke that this kind of code did actually work. Not really sure why, maybe thanks to some doctrine lazy loading behavior. Now we're only flushing once here, we need to hydrate manually the inverse field
Ok, I think it's time for this now 😅
It's been a while that I wanted to achieve this: we're calling too many
flush()
, because we're flushing every time an entity is created, although, the "Doctrine way" would be to only flush once per "root factory".This is actually possible thanks to all the work that have been done around "schedule for insert" stuff.
A problem that could have happened because of this, is with "after persist" callbacks : not calling
save()
for every entity would mean not calling their "after persist callbacks", but I was pleasantly surprised that this is an already solved problem, thanks to this PR 🎉What triggered me to do this now, was this issue raised by @mmarton
given this code:
there is a problem with the "placeholder solution" for inverse
OneToOne
: at some point, because we flush right after thecategory
is created, and becausestock
is already persisted (the doctrine way), it tries to save the "placeholder" 🤷I've tried a lot of different things, but nothing works with every combination possible of "cascade persist" stuff. But the problem is naturally fixed when we deffer the
flush()
call: we can safely remove the placeholder from the UoW, before any flush attempt is made.Another solution would be to use PHP 8.4 proxies, because the placeholder could be the real object, but that's more work, and it will only fix the problem for the last PHP version. Moreover both of these solutions are not mutually exclusive, and I'm planning to work on this at some point,(maybe in the same time when I'll use PHP 8.4 proxies for data provider as well).
It comes also with a performance boost:
locally, without Mongo and with DAMA enabled, the command
phpunit --exclude-group maker
runs in 10s now and in 15s before 🏃 ⚡FYI, before the fix, the whole testsuite was performing 3040 calls to
flush()
, and now, only 1300Surprisingly, there is no performance boost in the testsuite without dama.
Finally, I think this should be released in
2.3.x
, because the 2.4 release will be full of new features, which are not 100% ready.I've discovered yet another problem with the "placeholder" stuff for inversed one-to-one, when not using auto-generated ids: in case of
cascade: persist
, the placeholder was passed to$em->persist()
, and it was emitting aEntityMissingAssignedId
because the id is generated in the constructor of the entity, but the "placeholder" is created withnewInstanceWithoutConstructor()
.Then I decided to even deffer
$em->persist()
calls. This has the main advantage that we can do all the weird stuff we want, and only after that, doctrine joins the party. With the solution, I think we will finally be done with all that "inverse one to one" madness 🎉 😅